package com.senseuni.da.http

import android.os.Build
import android.os.SystemClock
import android.webkit.URLUtil
import com.elvishew.xlog.XLog
import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
import com.senseuni.da.BuildConfig
import com.senseuni.da.MyApplication
import com.senseuni.da.MyApplication.Companion.mqttHost
import com.senseuni.da.db.AppDatabase
import com.senseuni.da.db.entity.AppLogEntity
import com.senseuni.da.db.entity.UpgradeEntity
import com.senseuni.da.iot.MqttInfo
import com.senseuni.da.utils.AppUtils
import com.senseuni.da.utils.EventUtil
import com.senseuni.da.utils.SpUtil
import com.senseuni.da.utils.toJson
import io.reactivex.Observable
import io.reactivex.Observer
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.functions.Function
import io.reactivex.schedulers.Schedulers
import okhttp3.ResponseBody
import org.threeten.bp.ZonedDateTime
import org.threeten.bp.format.DateTimeFormatter
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.io.*


class ApiDataSource {

    companion object {
        val instance : ApiDataSource by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            ApiDataSource()
        }
    }

    private val mApiService: ApiService


    init {
//        mApiService = createRetrofit(AppUtils.getBaseUrl(MyApplication.app.applicationContext))
        XLog.d("baseUrl:" + BuildConfig.API_HOST)
        mApiService = createRetrofit(BuildConfig.API_HOST)
    }

    private fun createRetrofit(url: String): ApiService {
        return Retrofit.Builder()
            .baseUrl(url)
            .addConverterFactory(GsonConverterFactory.create())
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
            .client(OkHttpManager.getHttpClient())
            .build()
            .create(ApiService::class.java)
    }

    private fun createRequest(): BaseRequest {
        val request = BaseRequest()
        request.init()
//        var deviceId = MyApplication.DEVICE_ID
//        if (deviceId.isEmpty()) {
//            deviceId = AppUtils.getDeviceId()
//        }
//        XLog.d("deviceId:${deviceId}")
//        request.deviceId = AppUtils.base64Encode(deviceId)
//        request.idType = "imei"
//        val stochastic = UUID.randomUUID().toString()
//        request.key = AppUtils.generateKey(stochastic, deviceId)
//        request.stochastic = stochastic
//        request.versionCode = BuildConfig.VERSION_CODE
//        request.modelCode = Build.MODEL
        XLog.d(request.toJson())
        return request
    }

    fun init() : Observable<MqttInfo> {
        val request = createRequest()
        XLog.d("deviceId: " + MyApplication.DEVICE_ID + ",version:" + BuildConfig.VERSION_NAME)
        XLog.d(request.toJson())
        return mApiService.registry(request)
            .subscribeOn(Schedulers.io())
            .flatMap {
                XLog.d("register result:" + it.codeAndMessage)
                if (it.isSuccess) {
                    EventUtil.sendMessageEvent("Register Success")
                }
                mApiService.deviceEnv(
                    request
                )
            }
            .map { response: BaseResponse<DeviceEnvBean> ->
                XLog.d("env:%s", response.toJson())
                if (response.isSuccess) {
                    //http://121.40.192.194:1883
                    if (response.data.serverUrl.isNullOrEmpty()) {
                        throw Exception("Not found iot server host")
                    }
                    mqttHost = response.data.serverUrl!!.replace("http", "tcp")
                    EventUtil.sendMessageEvent("Get Dev env success")
                    return@map response.data
                }
                throw Exception(response.codeAndMessage)
            }
            .flatMap {
                XLog.d("start get date info")
                mApiService.getDateInfo(request)
            }
            .map {
                XLog.d("end get date info:" + it.toJson())
                if (it.isSuccess) {
                    if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
                        val timestamp = convertToTimestamp(it.data.currentTime)
                        XLog.d("get current time:" + it.data.currentTime + ", timestamp:" + timestamp)
                        SystemClock.setCurrentTimeMillis(timestamp)
                        XLog.d("sync time success.now time:" + System.currentTimeMillis())
                    }
                    return@map
                }
                throw Exception(it.codeAndMessage)
            }
            .flatMap {
                mApiService.deviceInfo(
                    request
                )
            }
            .map { response: BaseResponse<DeviceInfoBean> ->
                XLog.d("device info:%s", response.toJson())
                if (response.isSuccess) {
                    SpUtil.saveQRCode(response.data.code ?: "")
                    EventUtil.sendMessageEvent("Get device info success")
                    return@map response.data
                }
                throw Exception(response.codeAndMessage)
            }
            .flatMap {
                val mqttInfo = MqttInfo()
                it.deviceInfo?.apply {
                    mqttInfo.host = mqttHost
//                    mqttInfo.host = "tcp://121.40.192.194:1883"
                    mqttInfo.clientId = clientId.toString()
                    mqttInfo.productId = productId
                    mqttInfo.deviceId = deviceId.toString()
                    val username = "${secureId}|${System.currentTimeMillis()}"
                    val password = AppUtils.md5("${username}|${secureKey}")
                    mqttInfo.username = username
                    mqttInfo.password = password
                }
                Observable.just(mqttInfo)
            }
//            .subscribe({
//                MQTTManager.instance.init(it)
//            },{
//                XLog.e("Init Fail:" + it.message)
//                it.printStackTrace()
//            })
    }

    suspend fun applog(list: List<AppLogEntity>) : BaseResponse<Any> {
//        var deviceId = MyApplication.DEVICE_ID
//        if (deviceId.isEmpty()) {
//            deviceId = AppUtils.getDeviceId()
//        }
        val request = AppLogRequest()
        request.init()
//        request.deviceId = AppUtils.base64Encode(deviceId)
//        request.idType = "imei"
//        val stochastic = UUID.randomUUID().toString()
//        request.key = AppUtils.generateKey(stochastic, deviceId)
//        request.stochastic = stochastic
//        request.versionCode = BuildConfig.VERSION_CODE
//        request.deviceLogList = mutableListOf()
        list.forEach {
            val logInfo = LogInfo(it.log, it.logType, it.createTime)
            request.deviceLogList?.add(logInfo)
        }
//        XLog.d(request.toJson())
        return mApiService.uploadDeviceLog(request)
    }

    fun checkUpgrade(): Observable<UpgradeEntity> {
        val request = createRequest()
        return mApiService.checkUpgrade(request)
            .subscribeOn(Schedulers.io())
            .map { response: BaseResponse<UpgradeBean> ->
                XLog.d("upgrade:%s", response.toJson())
                if (response.isSuccess) {
                    //TODO
//                    response.data?.run {
//                        upgradeUrl = "https://api-smart-dev.oss-cn-hangzhou.aliyuncs.com/22/23/VID_20241201_120553.mp4"
//                        versionCode = 1001
//                        size = 10000L
//                        md5 = "123"
//                        version = "1.0.1"
//                    }
                    return@map response.data
                }
//                val deviceId = MyApplication.DEVICE_ID
//                val url = "https://api-smart-dev.oss-cn-hangzhou.aliyuncs.com/22/23/ads_1.0.4.apk"
//                val md5 = ""
//                val size = 100000L
//                val content = "测试更新"
//                val versionCode = 1004
//                val version = "1.0.4"
//                response.data = UpgradeBean(deviceId, url, md5, size, content, versionCode, version)
//                return@map response.data
                throw Exception(response.codeAndMessage)
            }
            .filter {
                if (it.versionCode == null) {
                    XLog.e("upgrade data error")
                    throw Exception("upgrade data error,no version code")
                }
                val currentVersion = BuildConfig.VERSION_CODE
                val latestVersion = it.versionCode!!
                XLog.d("currentVersion=${currentVersion},latestVersion=${latestVersion}")
                if (currentVersion >= latestVersion) {
                    XLog.d("no need upgrade")
                    return@filter false
                }
                true
            }
            .flatMap {
                val upgradeDao = AppDatabase.getInstance().upgradeDao()
                var upgrade = upgradeDao.findByVersion(it.versionCode!!)
                if (upgrade == null) {
                    upgrade = UpgradeEntity()
                    upgrade.url = it.upgradeUrl
                    upgrade.md5 = it.md5
                    upgrade.fileSize = it.size
                    upgrade.version = it.version
                    upgrade.versionCode = it.versionCode
                    upgrade.createTime = System.currentTimeMillis()
                    val id = upgradeDao.insert(upgrade)
                    upgrade.id = id
                }
                Observable.just(upgrade)
            }
//            .flatMap(Function<UpgradeBean, ObservableSource<Upgrade>> { (url, md5, size, device_version_code) ->
//                //save
//                val upgradeDao = getInstance().upgradeDao()
//                var upgrade = upgradeDao.findByVersion(device_version_code!!)
//                //                    if (upgrade == null) {
//                upgrade = Upgrade()
//                upgrade.url = url
//                upgrade.md5 = md5
//                upgrade.fileSize = size
//                upgrade.version = device_version_code
//                val id = upgradeDao.insert(upgrade)
//                upgrade.id = id
//                Observable.just(upgrade)
//            })
    }

    fun getWeatherInfo(): Observable<WeatherInfo> {
        val request = createRequest()
        return mApiService.getWeatherInfo(request)
            .subscribeOn(Schedulers.io())
            .map { response: BaseResponse<WeatherInfo> ->
                XLog.d("weather:%s", response.toJson())
                if (response.isSuccess) {
                    return@map response.data
                }
                throw Exception(response.codeAndMessage)
            }
    }

    fun getDateInfo(): Observable<DateInfo> {
        val request = createRequest()
        XLog.d("start getDateInfo")
        return mApiService.getDateInfo(request)
            .subscribeOn(Schedulers.io())
            .map { response: BaseResponse<DateInfo> ->
                XLog.d("end getDateInfo :%s", response.toJson())
                if (response.isSuccess) {
                    return@map response.data
                }
                throw Exception(response.codeAndMessage)
            }
    }

    fun downloadApk(fileUrl: String, onDownloadListener: OnDownloadListener?) {
        mApiService.downloadFile(fileUrl)
            .subscribeOn(Schedulers.io())
            .map<File>(Function { responseBody: ResponseBody ->
                val cacheDir = MyApplication.app.applicationContext.cacheDir
                val apkCacheDir = File(cacheDir.absolutePath + File.separator + "apk")
                if (!apkCacheDir.exists()) {
                    apkCacheDir.mkdirs()
                }
                val fileName = URLUtil.guessFileName(fileUrl, null, null)
                val apkFile = File(apkCacheDir.absolutePath, fileName)
                if (apkFile.exists()) {
                    apkFile.delete()
                }
                writeResponseBodyToDisk(fileName, apkFile, responseBody, onDownloadListener)
            })
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(object : Observer<File> {
                override fun onSubscribe(d: Disposable) {
                    onDownloadListener?.onStart()
                }

                override fun onNext(apkFile: File) {
                    onDownloadListener?.onSuccess(apkFile.absolutePath)
                }

                override fun onError(e: Throwable) {
                    e.printStackTrace()
                    XLog.e("download fail")
                    onDownloadListener?.onFail(Exception(e.message))
                }

                override fun onComplete() {}
            })
    }

    @Throws(IOException::class)
    fun writeResponseBodyToDisk(
        fileName: String?,
        destFile: File?,
        body: ResponseBody,
        onDownloadListener: OnDownloadListener?
    ): File? {
        var inputStream: InputStream? = null
        var outputStream: OutputStream? = null
        val fileSize = body.contentLength()
        var fileSizeDownloaded: Long = 0
        val fileReader = ByteArray(1024 * 64)
        var read: Int
        inputStream = body.byteStream()
        outputStream = FileOutputStream(destFile)
        while (inputStream.read(fileReader).also { read = it } != -1) {
            outputStream.write(fileReader, 0, read)
            fileSizeDownloaded += read.toLong()
            onDownloadListener?.onProgress(fileSizeDownloaded, fileSize)
        }
        outputStream.flush()
        return destFile
    }

    private fun convertToTimestamp(isoString: String): Long {
        val formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME
        val zonedDateTime = ZonedDateTime.parse(isoString, formatter)
        return zonedDateTime.toInstant().toEpochMilli()
    }

}